Skip to main content
  1. Writing/

Generating Windows 10 Notifications With PowerShell

·543 words

I recently moved all my development boxes to Windows. That’s right, all those Mac machines, with the exception of one that I need for testing, have been Boot Camp-ed to use the latest and greatest of the Windows 10 ecosystem. As part of this process, I started building some automation scripts - I’ve had that for macOS in the form of shell scripts that I could run whenever I re-installed the system, but nothing like that for Windows. This led me down the rabbithole of trying to figure out how to use PowerShell to do things that I’ve been doing in Bash for the past couple of years. And before someone says, “But WSL - you can just re-use your Bash scripts!” Yes and no - I had some interesting dependencies that don’t quite translate as well to Bash, so here I am, stuck dealing with PowerShell.

Here is the thing about installation/system provisioning scripts - they can run for some time. I wanted to be alerted when they finish specific steps, error out, or are finished. The standard approach might have been using the console output, but that is just basic. Instead, I resorted to using native Windows 10 APIs, that would allow me to produce toast notifications, as if I am running a real application.

Here is how I did it (you can also grab the code in a Gist):

function Show-Notification {
    [cmdletbinding()]
    Param (
        [string]
        $ToastTitle,
        [string]
        [parameter(ValueFromPipeline)]
        $ToastText
    )

    [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
    $Template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)

    $RawXml = [xml] $Template.GetXml()
    ($RawXml.toast.visual.binding.text|where {$_.id -eq "1"}).AppendChild($RawXml.CreateTextNode($ToastTitle)) > $null
    ($RawXml.toast.visual.binding.text|where {$_.id -eq "2"}).AppendChild($RawXml.CreateTextNode($ToastText)) > $null

    $SerializedXml = New-Object Windows.Data.Xml.Dom.XmlDocument
    $SerializedXml.LoadXml($RawXml.OuterXml)

    $Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml)
    $Toast.Tag = "PowerShell"
    $Toast.Group = "PowerShell"
    $Toast.ExpirationTime = [DateTimeOffset]::Now.AddMinutes(1)

    $Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("PowerShell")
    $Notifier.Show($Toast);
}

A couple of things are going on in the Show-Notification function. First, the parameters. I am a fan of passing things through pipes (also known as |). That is, I like to pass the output of a previous command to the next one seamlessly. By adding the ValueFromPipeline attribute to $ToastText, I am telling PowerShell that this parameter, when omitted, should just be taken from whatever ran before the function call.

Next, I am getting the native toast template that ships in Windows 10. There are a couple that I can use, but the one of most interest is ToastText02 - a simple template that allows specifying a toast title and a message. The interesting part here is that for me to manipulate the toast notification, I need to know the XML structure of the template. The latest documentation, unfortunately, is not that good in this regard. There is content on the toast schema but it does little in telling me what the actual XML looks like for the template I am using. I had to dig through legacy content to find real XML examples - and that was good enough!

Using the newly acquired XML knowledge, I now knew what I needed to modify. I used .NET XML functions to add my text to the notification bindings, and I was on my way, right after serializing the XML into a ToastNotification instance. All that was left was using CreateToastNotifier to create the notification manager instance, and show the actual toast.

Example of a PowerShell script displaying a native Windows 10 notification